#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>
#include <queue>
#include <vector>
using namespace std;

#define SIZ 128

#define ENTRY_NUM 3
#define EXIT_NUM 2
#define THREAD_NUM (ENTRY_NUM + EXIT_NUM) 
#define TOTAL_NUM 10

#define ENTRY 0
#define EXIT 1

#define OK_MSG 0
#define DEPART_REQ_MSG 1
#define ENTER_REQ_MSG 2
#define REL_MSG 3

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;       

typedef struct timestamp{
    unsigned long clock;
    unsigned long tid;
    unsigned long num;
    int pos;
    bool isEntry;
    int index;
} TS;

typedef queue<char*> QUEUE;
QUEUE msg_queue[THREAD_NUM];
bool hasEvent[THREAD_NUM];
pthread_t tid[THREAD_NUM];


void* enter(void*);
void* depart(void*);
void msg_process(TS*, QUEUE*, bool*, int);
void wait_for_reply(TS*, QUEUE*, bool*, bool*, int);
char** split(char*, char, int);
bool check(bool*);
int find(pthread_t*, pthread_t );
bool is_before(TS* , char** );
void reset_clock(TS*, char**);
bool at_top(QUEUE* , int );
void send_msg(TS*, int, int);
void remove(QUEUE*, int pos);

int main(int argc, const char *argv[])
{
    for (int i = 0; i <THREAD_NUM; i++) {
        hasEvent[i] = false;
    }
    printf("The number of threads is %d.\n",THREAD_NUM); 


    int ret,i;
    pthread_t* ptid = tid;
    for (i = 0; i < ENTRY_NUM; i++) {
        ret = pthread_create(ptid++, NULL, enter, NULL);
        if(ret != 0){
            printf("Entry Thread creation error!\n");
            exit(1);
        }
    }
    for (i = 0; i < EXIT_NUM; i++) {
        ret = pthread_create(ptid++, NULL, depart, NULL);
        if(ret != 0){
            printf("Exit Thread creation error!\n");
            exit(1);
        }
    }


    char buf[SIZ], *cmd;
    char** cpp;
    int which;
    while(1){
        fgets(buf, SIZ-1, stdin);
        int len = strlen(buf);
        buf[len-1] = '\0';
        cpp = split(buf, ' ', 2);
        cmd = cpp[0];
        which = atoi(cpp[1]);
        if(!strcmp(cmd, "enter") && which>0 && which<=ENTRY_NUM || 
            !strcmp(cmd, "exit") && which>ENTRY_NUM && which<=THREAD_NUM){
                hasEvent[which-1] = true;
        } else {
            perror("Incorrect syntax! Input again!\n");
        }
    }
    return 0;
}

void* enter(void* args)
{
    pthread_t local_tid = pthread_self();
    
    //local info of a thread
    TS ts;
    ts.clock = 1;
    ts.tid = (unsigned long) local_tid;
    ts.num = TOTAL_NUM;
    ts.pos = find(tid, local_tid);
    ts.isEntry = true;
    ts.index = ts.pos+1;

    QUEUE request_queue; //request queue 
    bool allowed[THREAD_NUM];


    while(1){
       while(!(msg_queue[ts.pos].empty())){
           printf("In Entry %d...\n",ts.index);
           msg_process(&ts, &request_queue, allowed, ENTRY );
       } 
       while(hasEvent[ts.pos]){
           //send message to all other threads;
           for(int i=0; i<THREAD_NUM; i++){
               send_msg(&ts, i, ENTER_REQ_MSG);
           }
           wait_for_reply(&ts, &request_queue, allowed, hasEvent,ENTRY);
           hasEvent[ts.pos] = false;
       }
    }
}

void* depart(void* args)
{
    pthread_t local_tid = pthread_self();
    
    //local information
    int total_num = TOTAL_NUM;
    TS ts;
    ts.clock= 1;
    ts.tid = (unsigned long) local_tid;
    ts.num = TOTAL_NUM;
    ts.pos = find(tid, local_tid);
    ts.isEntry = false;
    ts.index = ts.pos-ENTRY_NUM+1;

    QUEUE request_queue; //request queue
    bool allowed[THREAD_NUM];


    while(1){
       if(!(msg_queue[ts.pos].empty())){
            printf("In DEPART %d...\n",ts.index);
            msg_process(&ts, &request_queue, allowed, EXIT);
       } 
       while(hasEvent[ts.pos]){
           //send the message to all other threads
           for(int i=0; i<THREAD_NUM; i++){
               send_msg(&ts, i, DEPART_REQ_MSG);
           }
           wait_for_reply(&ts, &request_queue, allowed, hasEvent,EXIT);
       }
    }
}

void msg_process(TS* pts, QUEUE* request_queue, bool* allowed, int option)
{
    if (pts->isEntry) 
        printf("In Entry %d, msg_processing...\n", pts->index);
    else
        printf("In DEPART %d, msg_processing...\n", pts->index);

    QUEUE* q = &(msg_queue[pts->pos]);
    char* msg;
    int pos, key;

    while(!q->empty()){
        if (q->front()!=NULL) {
            char** parts = split(q->front(), ':', 4);
            reset_clock(pts, parts);

            key = atoi(parts[0]);
            pos = atoi(parts[3]);
            switch (key){
                case OK_MSG:
                    printf("OK\n");
                    allowed[pos] = true;
                    if(check(allowed)&&at_top(request_queue,pts->pos)){
                        if(option == EXIT)
                            pts->num++;
                        else if(option == ENTRY)
                            pts->num--;
                        else {
                            perror("Neither entry nor exit");
                            exit(1);
                         }
                    }
                    break;
                case ENTER_REQ_MSG:
                    printf("ENTER\n");
                    if(is_before(pts, parts)){
                        request_queue->push(q->front());
                    }else{
                        send_msg(pts, pos, OK_MSG);
                        pts->num--;
                    }
                    break;
                case DEPART_REQ_MSG:
                    printf("DEPART\n");
                    send_msg(pts, pos, OK_MSG);
                    pts->num++;
                    break;
                case REL_MSG:
                    printf("RELEASE\n");
                    remove(request_queue, pos);
                    break;
                default:
                    printf("Unknown key!\n");
                    exit(1);
            }

            msg = q->front();
            q->pop();
            delete []msg;
        }
    }
}

void wait_for_reply(TS* pts, QUEUE* request_queue, bool* allowed, bool* hasEvent, int option)
{
    int pos = pts->pos;
    QUEUE* q = &(msg_queue[pos]);
    if(!(q->empty())){
        msg_process(pts, request_queue, allowed, option);
    } 
    
    hasEvent[pos] = false;
}

void send_msg(TS* pts, int pos, int option)
{
    if(option!=OK_MSG && option!=ENTER_REQ_MSG && option!=DEPART_REQ_MSG && option!=REL_MSG){
        printf("Unknown option in send_msg");
        exit(1);
    }
    char* msg = new char[SIZ];
    sprintf(msg, "%d:%lu:%lu:%d", option, pts->clock, pts->tid, pts->pos);
    msg_queue[pos].push(msg);
}

char** split(char* msg, char delim, int count)
{
    char** ret = new char* [count];
    int j = 1;
    char* p = msg;
    ret[0] = msg;

    while(*p!='\0') {
        if(*p == delim){
            *p = '\0';
            ret[j++] = p+1;
        }
        p++;
    }
    return ret;
}

bool check(bool* allowed)
{
    int i=0;
    for (i = 0; i < THREAD_NUM; i++) {
        if(!allowed[i]) return false;
    }

    return true;
}

int find(pthread_t* ptid, pthread_t tid)
{
    int i;
    for (i = 0; i < THREAD_NUM; i++) {
        if (pthread_equal(ptid[i],tid)) {
            return i;
        }
    }
}

bool is_before(TS* ts, char** parts)
{
    unsigned long clock = atoi(parts[2]);
    if (ts->clock > clock ) {
        return false;
    } else if(ts->clock < clock){
        return true;
    }

    unsigned long tid = atoi(parts[3]);
    if(ts->tid > tid){
        return false;
    }
    return true;
}

void reset_clock(TS* ts, char** parts)
{
    unsigned long clock = atoi(parts[2]);
    if (ts->clock < clock){
        ts->clock = clock;
    }
}

bool at_top(QUEUE* rq, int pos)
{
    char* top_ele = rq->front();
    if(top_ele == NULL) return false;
    char** parts = split(top_ele, ':', 4);
    int pos1 = atoi(parts[4]);
    if(pos==pos1){
        return true;
    }
    return false;
}

void remove(QUEUE* rq, int pos)
{
    char* str;
    char** parts;
    int count = rq->size();
    for (int i = 0; i < count; i++) {
        str = rq->front();
        parts = split(str, ':', 4);
        if(pos == atoi(parts[3])){
            rq->pop();
            delete []str;
        }else{
            rq->pop();
            rq->push(str);
        }
        delete []parts;
    }
}
